# This defines a deployment "recipe" that you can feed to switchtower
# (http://manuals.rubyonrails.com/read/book/17). It allows you to automate
# (among other things) the deployment of your application.

require 'capistrano/recipes/deploy/scm'
require 'capistrano/recipes/deploy/strategy'

# =============================================================================
# REQUIRED VARIABLES
# =============================================================================
# You must always specify the application and repository for every recipe. The
# repository must be the URL of the repository you want this recipe to
# correspond to. The deploy_to path must be the path on each machine that will
# form the root of the application path.

set :application, "opinion"
set :repository, "http://opinionforum.googlecode.com/svn/trunk/"

# =============================================================================
# ROLES
# =============================================================================
# You can define any number of roles, each of which contains any number of
# machines. Roles might include such things as :web, or :app, or :db, defining
# what the purpose of each machine is. You can also specify options that can
# be used to single out a specific subset of boxes in a particular role, like
# :primary => true.

role :web, "tellurium.jadedpixel.com"
role :app, "tellurium.jadedpixel.com"
role :db,  "tellurium.jadedpixel.com", :primary => true

# =========================================================================
# These variables may be set in the client capfile if their default values
# are not sufficient.
# =========================================================================

set :scm, :subversion
set :deploy_via, :checkout

set(:deploy_to) { "/u/apps/#{application}" }
set(:revision)  { source.head }

# =========================================================================
# These variables should NOT be changed unless you are very confident in
# what you are doing. Make sure you understand all the implications of your
# changes if you do decide to muck with these!
# =========================================================================

set(:real_revision)     { source.local.query_revision(revision) { |cmd| with_env("LC_ALL", "C") { `#{cmd}` } } }
set(:source)            { Capistrano::Deploy::SCM.new(scm, self) }
set(:strategy)          { Capistrano::Deploy::Strategy.new(deploy_via, self) }

set(:release_name)      { set :deploy_timestamped, true; Time.now.utc.strftime("%Y%m%d%H%M%S") }
set(:releases_path)     { File.join(deploy_to, "releases") }
set(:shared_path)       { File.join(deploy_to, "shared") }
set(:current_path)      { File.join(deploy_to, "current") }
set(:release_path)      { File.join(releases_path, release_name) }

set(:releases)          { capture("ls -x #{releases_path}").split.sort }
set(:current_release)   { File.join(releases_path, releases.last) }
set(:previous_release)  { File.join(releases_path, releases[-2]) }

set(:current_revision)  { capture("cat #{current_path}/REVISION").chomp }
set(:latest_revision)   { capture("cat #{current_release}/REVISION").chomp }
set(:previous_revision) { capture("cat #{previous_release}/REVISION").chomp }

set(:run_method)        { fetch(:use_sudo, true) ? :sudo : :run }

# some tasks, like symlink, need to always point at the latest release, but
# they can also (occassionally) be called standalone. In the standalone case,
# the timestamped release_path will be inaccurate, since the directory won't
# actually exist. This variable lets tasks like symlink work either in the
# standalone case, or during deployment.
set(:latest_release) { exists?(:deploy_timestamped) ? release_path : current_release }


# =============================================================================
# OPTIONAL VARIABLES
# =============================================================================
# set :deploy_to, "/path/to/app" # defaults to "/u/apps/#{application}"
set :user, ENV['OPINION_SVN_USER'] || ENV['USER']           # defaults to the currently logged in user
# set :scm, :darcs               # defaults to :subversion
# set :svn, "/path/to/svn"       # defaults to searching the PATH
# set :darcs, "/path/to/darcs"   # defaults to searching the PATH
# set :cvs, "/path/to/cvs"       # defaults to searching the PATH
# set :gateway, "gate.host.com"  # default to no gateway

set :checkout_path, "#{shared_path}/checkout"

# =============================================================================
# SSH OPTIONS
# =============================================================================
# ssh_options[:keys] = %w(/path/to/my/key /path/to/another/key)
# ssh_options[:port] = 25

# =============================================================================
# TASKS
# =============================================================================
# Define tasks that run on all (or only some) of the machines. You can specify
# a role (or set of roles) that each task should be executed on. You can also
# narrow the set of servers to a subset of a role by specifying options, which
# must match the options given for the servers to select (like :primary => true)

# Tasks may take advantage of several different helper methods to interact
# with the remote server(s). These are:
#
# * run(command, options={}, &block): execute the given command on all servers
#   associated with the current task, in parallel. The block, if given, should
#   accept three parameters: the communication channel, a symbol identifying the
#   type of stream (:err or :out), and the data. The block is invoked for all
#   output from the command, allowing you to inspect output and act
#   accordingly.
# * sudo(command, options={}, &block): same as run, but it executes the command
#   via sudo.
# * delete(path, options={}): deletes the given file or directory from all
#   associated servers. If :recursive => true is given in the options, the
#   delete uses "rm -rf" instead of "rm -f".
# * put(buffer, path, options={}): creates or overwrites a file at "path" on
#   all associated servers, populating it with the contents of "buffer". You
#   can specify :mode as an integer value, which will be used to set the mode
#   on the file.
# * render(template, options={}) or render(options={}): renders the given
#   template and returns a string. Alternatively, if the :template key is given,
#   it will be treated as the contents of the template to render. Any other keys
#   are treated as local variables, which are made available to the (ERb)
#   template.

# You can use "transaction" to indicate that if any of the tasks within it fail,
# all should be rolled back (for each task that specifies an on_rollback
# handler).

desc "Upload local (secret) database.yml to all the servers"
task :upload_config do 
  #require 'environment'
  
  put File.read('config/database.yml'), "#{shared_path}/database.yml", :mode => 0644
  put File.read('config/recaptcha.yml'), "#{shared_path}/recaptcha.yml", :mode => 0644
end


task :aquire_db_config do
  run "cp #{shared_path}/database.yml #{release_path}/config/database.yml"
end

task :aquire_mongrel_config do
  run "cp #{shared_path}/mongrel_cluster.yml #{release_path}/config/mongrel_cluster.yml"
end

task :aquire_recaptcha_config do
  run "cp #{shared_path}/recaptcha.yml #{release_path}/config/recaptcha.yml"
end

desc "Show application statistics in real-time"
task :watch_status, :roles => :app do
  count = 0
  last = Time.now
  run "tail -f #{shared_path}/log/production.log" do |ch, stream, out|
    puts "#{ch[:host]}: #{out}" and break if stream == :err

    count += 1 if out =~ /^Completed in/

    if Time.now - last >= 1
      puts "%2d rps" % count
      count = 0
      last = Time.now
    end
  end
end

# Reap the current fastcgi processes. The server has a spinner daemon which 
# Restarts them instantly
task :restart, :roles => :app do 
  run "cd #{current_path} && mongrel_rails cluster::restart"
end

task :update_code, :roles => [:app, :db, :web] do
  version = real_revision
  
  run <<-CMD
  if [[ -d #{checkout_path} ]]; 
  then svn up -r#{version} -q #{checkout_path}; 
  else svn co -r#{version} -q #{repository} #{checkout_path}; 
  fi
  CMD
  
  run <<-CMD
    cp -r #{checkout_path} #{release_path} &&
    rm -rf #{release_path}/log &&
    ln -nfs #{shared_path}/log #{release_path}/log &&
    ln -nfs #{shared_path}/tmp #{release_path}/tmp &&
    ln -nfs #{shared_path}/files #{release_path}/public/files
  CMD
end

# Symlink the log directory to shared/log and the files directory to shared/files
desc 'Commit the symbolic links'
task :symlink do
  on_rollback { run "ln -nfs #{previous_release} #{current_path}" }
  
  run "ln -nfs #{release_path} #{current_path}"
end

task :migrate, :roles => [:db] do
  run "cd #{release_path} && RAILS_ENV=production rake db:migrate"
end

desc "Set up the expected application directory structure on all boxes"
task :setup, :roles => [:app, :db, :web] do
  run <<-CMD
    mkdir -p -m 775 #{releases_path} #{shared_path}/system &&
    mkdir -p -m 777 #{shared_path}/log &&
    mkdir -p -m 777 #{shared_path}/tmp &&
    mkdir -p -m 777 #{shared_path}/files
  CMD
end

desc "A task demonstrating the use of transactions."
task :deploy do
  setup
  
  transaction do
    update_code    
    aquire_db_config
    aquire_mongrel_config
    aquire_recaptcha_config
    symlink
    migrate
  end

  restart
end


# Temporarily sets an environment variable, yields to a block, and restores
# the value when it is done.
def with_env(name, value)
  saved, ENV[name] = ENV[name], value
  yield
ensure
  ENV[name] = saved
end
